package com.example.s3demo.service.impl

import com.example.s3demo.config.SystemConfig
import com.example.s3demo.model.*
import com.example.s3demo.service.S3Service
import com.example.s3demo.utils.*
import org.springframework.stereotype.Service
import java.io.*
import java.net.URLEncoder
import java.util.*

@Service
class S3ServiceImpl(
    private val systemConfig: SystemConfig,
) : S3Service {

    override fun createBucket(bucketName: String): Bucket {
        val dirPath = systemConfig.dataPath + bucketName
        val dir = File(dirPath);
        if (!dir.exists()) {
            dir.mkdir()
        }
        val bucket = Bucket()
        bucket.name = bucketName
        bucket.creationDate = DateUtil.dateFormatToSecond(Date())
        return bucket
    }

    override fun deleteBucket(bucketName: String) {
        val dirPath = systemConfig.dataPath + bucketName
        FileUtil.delete(dirPath)
    }

    override fun listBuckets(): List<Bucket> {
        val bucketList = ArrayList<Bucket>()
        val dirPath = systemConfig.dataPath
        val dir = File(dirPath)
        if (dir.exists()) {
            dir.listFiles()?.let {
                for (file in it) {
                    if (file.isDirectory) {
                        val bucket = Bucket()
                        bucket.name = file.name
                        bucket.creationDate = FileUtil.getCreationTime(file.absoluteFile).toString()
                        bucketList.add(bucket)
                    }
                }
            }
        }
        return bucketList
    }

    override fun headBucket(bucketName: String): Boolean {
        val dirPath = systemConfig.dataPath + bucketName
        return File(dirPath).exists()
    }

    override fun listObjects(bucketName: String, prefix: String): List<S3Object> {
        var prefix = prefix
        if (StringUtil.isEmpty(prefix)) {
            prefix = "/"
        } else {
            if (!prefix.startsWith("/")) {
                prefix = "/$prefix"
            }
        }
        val s3Objects = ArrayList<S3Object>()
        val dirPath = systemConfig.dataPath + bucketName + prefix
        val dir = File(dirPath)
        dir.listFiles()?.let {
            for (file in it) {
                val s3Object = S3Object()
                s3Object.bucketName = bucketName
                val objectMetadata = ObjectMetadata()
                objectMetadata.fileName = file.name
                if (!file.isDirectory) {
                    s3Object.key = file.name
                    objectMetadata.contentLength = FileUtil.getFileSize(file)
                    objectMetadata.contentType = FileUtil.getContentType(file.name)
                    objectMetadata.lastModified = FileUtil.getLastModifyTime(file).toString()
                } else {
                    s3Object.key = file.name + "/"
                }
                s3Object.metadata = objectMetadata
                s3Objects.add(s3Object)
            }
        }
        return s3Objects
    }

    override fun headObject(bucketName: String, objectKey: String): HashMap<String, String> {
        val headInfo = HashMap<String, String>()
        val filePath: String = systemConfig.dataPath + bucketName + "/" + objectKey
        val file = File(filePath)
        if (file.exists()) {
            try {
                headInfo["Content-Disposition"] = "filename=" + URLEncoder.encode(file.name, "utf-8")
                headInfo["Content-Length"] = "${FileUtil.getFileSize(file)}"
                headInfo["Content-Type"] = FileUtil.getContentType(file.name)
                headInfo["Last-Modified"] = "${FileUtil.getLastModifyTimeGMT(file)}"
            } catch (e: Exception) {
                e.printStackTrace()
            }
        } else {
            headInfo["NoExist"] = "1"
        }
        return headInfo
    }

    override fun getObject(bucketName: String, objectKey: String): S3ObjectInputStream? {
        val filePath: String = systemConfig.dataPath + bucketName + "/" + objectKey
        val file = File(filePath)
        if (file.exists()) {
            val fileByte = FileUtil.getFile(filePath)
            val inputStream: InputStream = ByteArrayInputStream(fileByte)

            val metadata = ObjectMetadata()
            metadata.contentLength = fileByte!!.size.toLong()
            metadata.contentType = FileUtil.getContentType(file.name)
            metadata.fileName = file.name
            metadata.lastModified = FileUtil.getLastModifyTime(file)!!
            return S3ObjectInputStream(metadata, inputStream)
        }
        return null
    }

    override fun deleteObject(bucketName: String, objectKey: String) {
        val filePath = systemConfig.dataPath + bucketName + "/" + objectKey
        FileUtil.delete(filePath)
    }

    override fun putObject(bucketName: String, objectKey: String, inputStream: InputStream) {
        createBucket(bucketName)
        val filePath: String = systemConfig.dataPath + bucketName + "/" + objectKey
        if (filePath.endsWith("/")) {
            val fileDir = File(filePath)
            if (!fileDir.exists()) {
                fileDir.mkdirs()
            }
        } else {
            val filePathList = filePath.split("\\/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            val result = StringBuilder()
            for (i in 0 until filePathList.size - 1) {
                result.append(filePathList[i]).append("/")
            }
            val fileDirPath = result.toString()
            val fileDir = File(fileDirPath)
            if (!fileDir.exists()) {
                fileDir.mkdirs()
            }
            FileUtil.saveFile(filePath, inputStream)
        }
    }

    override fun copyObject(
        sourceBucketName: String,
        sourceObjectKey: String,
        targetBuckName: String,
        targetObjectKey: String
    ) {
        val sourceFilePath: String = systemConfig.dataPath + sourceBucketName + "/" + sourceObjectKey
        createBucket(targetBuckName)
        val targetFilePath: String = systemConfig.dataPath + targetBuckName + "/" + targetObjectKey
        val filePathList = targetFilePath.split("\\/".toRegex()).dropLastWhile { it.isEmpty() }
            .toTypedArray()
        val result = StringBuilder()
        for (i in 0..<filePathList.size - 1) {
            result.append(filePathList[i]).append("/")
        }
        val fileDirPath = result.toString()
        val fileDir = File(fileDirPath)
        if (!fileDir.exists()) {
            fileDir.mkdirs()
        }
        try {
            FileUtil.copyFile(sourceFilePath, targetFilePath)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun initiateMultipartUpload(bucketName: String, objectKey: String): InitiateMultipartUploadResult {
        createBucket(bucketName)
        val multipartUploadResult = InitiateMultipartUploadResult()
        multipartUploadResult.bucket = bucketName
        multipartUploadResult.objectKey = objectKey
        val filePath: String = systemConfig.dataPath + bucketName + "/" + objectKey
        val filePathList = filePath.split("\\/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        val result = StringBuilder()
        for (i in 0..<filePathList.size - 1) {
            result.append(filePathList[i]).append("/")
        }
        val fileDirPath = result.toString()
        val fileDir = File(fileDirPath)
        if (!fileDir.exists()) {
            fileDir.mkdirs()
        }
        val uploadID: String = CommonUtil.newGuid()
        val tempPath: String = systemConfig.tempPath + "/" + uploadID + "/"
        val tempDir = File(tempPath)
        if (!tempDir.exists()) {
            tempDir.mkdirs()
        }
        multipartUploadResult.uploadId = uploadID
        return multipartUploadResult
    }

    override fun uploadPart(
        bucketName: String,
        objectKey: String,
        partNumber: Int,
        uploadId: String,
        inputStream: InputStream
    ): PartETag {
        val tempPartFilePath: String = systemConfig.tempPath + "/" + uploadId + "/" + partNumber + ".temp"
        val file = File(tempPartFilePath)
        if (!file.exists()) {
            val filePath: String = systemConfig.dataPath + bucketName + "/" + objectKey
            val filePathList = filePath.split("\\/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            val result = StringBuilder()
            for (i in 0 until filePathList.size - 1) {
                result.append(filePathList[i]).append("/")
            }
            val fileDirPath = result.toString()
            val partFilePath = "$fileDirPath$partNumber.part"
            FileUtil.saveFile(partFilePath, inputStream)
            try {
                FileUtil.writeFile(tempPartFilePath, EncryptUtil.encryptByDES(partFilePath))
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        val eTag = FileUtil.readFileContent(tempPartFilePath)
        val partETag = PartETag(partNumber, eTag!!)
        return partETag
    }

    override fun completeMultipartUpload(
        bucketName: String,
        objectKey: String,
        uploadId: String,
        compMPU: CompleteMultipartUpload
    ): CompleteMultipartUploadResult {
        val merageFilePath: String = systemConfig.dataPath + bucketName + "/" + objectKey
        val merageFile = File(merageFilePath)
        if (!merageFile.exists()) {
            val partETagList: List<PartETag> = compMPU.partETags
            var check = true
            for (partETag in partETagList) {
                val tempPartFilePath: String =
                    (systemConfig.tempPath + "/" + uploadId + "/" + partETag.partNumber) + ".temp"
                val eTag = FileUtil.readFileContent(tempPartFilePath)
                if (!partETag.geteTag().equals(eTag)) {
                    check = false
                    break
                }
            }
            if (check) {
                partETagList.sortedWith { o1, o2 -> o1.partNumber - o2.partNumber }
                try {
                    val destOutputStream = BufferedOutputStream(FileOutputStream(merageFilePath))
                    for (partETag in partETagList) {
                        val file = File(EncryptUtil.decryptByDES(partETag.geteTag()))
                        val fileBuffer = ByteArray(1024 * 1024 * 5)
                        var readBytesLength = 0
                        val sourceInputStream = BufferedInputStream(FileInputStream(file))
                        while ((sourceInputStream.read(fileBuffer).also { readBytesLength = it }) != -1) {
                            destOutputStream.write(fileBuffer, 0, readBytesLength)
                        }
                        sourceInputStream.close()
                    }
                    destOutputStream.flush()
                    destOutputStream.close()
                } catch (e: java.lang.Exception) {
                    e.printStackTrace()
                }
            }
        }
        var eTag = ""
        try {
            eTag = EncryptUtil.encryptByMD5("$bucketName/$objectKey")
        } catch (e: Exception) {
            e.printStackTrace()
        }
        val complateResult = CompleteMultipartUploadResult(
            bucketName, objectKey,
            eTag
        )
        return complateResult
    }

}